﻿namespace MvvmTools.NotifyPropertyChanged
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics.Contracts;
    using System.Linq.Expressions;
    using JetBrains.Annotations;

    /// <summary>
    /// Change's listener.
    /// </summary>
    /// <typeparam name="T">Type of target property.</typeparam>
    internal class ChangeListener<T> : ChangeListenerBase, IChangeListener<T> where T : INotifyPropertyChanged
    {
        // ReSharper disable once StaticFieldInGenericType
        private static readonly AccessorCache AccessorCache = new AccessorCache();

        /// <summary>
        /// Initializes a new instance of the <see cref="ChangeListener{T}"/> class.
        /// </summary>
        /// <param name="parent">
        /// The parent.
        /// </param>
        /// <param name="getter">
        /// The getter.
        /// </param>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        protected ChangeListener([CanBeNull] ChangeListenerBase parent, [NotNull] Func<object, INotifyPropertyChanged> getter, [CanBeNull] string propertyName)
            : base(parent, getter, propertyName)
        {
            Contract.Requires<ArgumentNullException>(getter != null);
        }

        /// <summary>
        /// Subscribe to property changes.
        /// </summary>
        /// <param name="propertySelector">
        /// The property selector.
        /// </param>
        /// <typeparam name="TProperty">
        /// The property.
        /// </typeparam>
        /// <returns>
        /// Subscription to property changes.
        /// </returns>
        public IChangeListener<TProperty> OnChange<TProperty>(Expression<Func<T, TProperty>> propertySelector)
            where TProperty : INotifyPropertyChanged
        {
            Contract.Requires<ArgumentNullException>(propertySelector != null);
            Contract.Ensures(Contract.Result<IChangeListener<TProperty>>() != null);

            var accessor = AccessorCache.GetAccessor(propertySelector);
            return new ChangeListener<TProperty>(this, o => accessor.Getter((T)o), accessor.Name);
        }

        /// <summary>
        /// Subscription to value changed event.
        /// </summary>
        /// <param name="propertySelector">
        /// The property selector.
        /// </param>
        /// <typeparam name="TProperty">
        /// The property.
        /// </typeparam>
        /// <returns>
        /// Subscription to value changes.
        /// </returns>
        public IPropertyObservable OnValueChange<TProperty>(Expression<Func<T, TProperty>> propertySelector)
        {
            Contract.Requires<ArgumentNullException>(propertySelector != null);
            Contract.Ensures(Contract.Result<IPropertyObservable>() != null);

            return new ChangeListener<INotifyPropertyChanged>(this, o => null, propertySelector.GetParameterName());
        }

        /// <inheritdoc />
        public IPropertyObservable OnObservableChange<TElement>(Expression<Func<T, IObservable<TElement>>> propertySelector)
        {
            Contract.Requires<ArgumentNullException>(propertySelector != null);
            Contract.Ensures(Contract.Result<IPropertyObservable>() != null);

            var accessor = AccessorCache.GetAccessor(propertySelector);
            return new ObservableNotificationListener<T, TElement>(this, accessor.Getter, propertySelector.GetParameterName());
        }

        /// <summary>
        /// Subscription to value changed event for all changes.
        /// </summary>
        /// <returns>
        /// Subscription to value changes.
        /// </returns>
        public IPropertyObservable OnAnyChanges()
        {
            Contract.Ensures(Contract.Result<IPropertyObservable>() != null);

            return new ChangeListener<INotifyPropertyChanged>(this, o => null, null);
        }
    }
}